package org.tinygroup.parsedsql.impl;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import javax.sql.DataSource;

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.jdbc.core.PreparedStatementCreatorFactory;
import org.springframework.jdbc.core.SqlParameter;
import org.springframework.jdbc.core.SqlRowSetResultSetExtractor;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
import org.springframework.jdbc.support.rowset.SqlRowSet;
import org.tinygroup.context.Context;
import org.tinygroup.logger.LogLevel;
import org.tinygroup.logger.Logger;
import org.tinygroup.logger.LoggerFactory;
import org.tinygroup.parsedsql.JDBCNamedSqlExecutor;
import org.tinygroup.parsedsql.ParsedSql;
import org.tinygroup.parsedsql.ResultSetCallback;
import org.tinygroup.parsedsql.SQLParser;
import org.tinygroup.parsedsql.base.DatabaseType;
import org.tinygroup.parsedsql.util.NamedParameterUtils;

/**
 * 
 * @author ballackhui
 *
 */
public class SimpleJDBCNamedSqlExecutor implements JDBCNamedSqlExecutor {

	/** Default maximum number of entries for this template's SQL cache: 256 */
	public static final int DEFAULT_CACHE_LIMIT = 256;

	private volatile int cacheLimit = DEFAULT_CACHE_LIMIT;
	
	private static final Logger LOGGER=LoggerFactory.getLogger(SimpleJDBCNamedSqlExecutor.class);
	
	private SQLParser sqlParser;

	/** Cache of original SQL String to ParsedSql representation */
	@SuppressWarnings("serial")
	private final Map<String, ParsedSql> parsedSqlCache = new LinkedHashMap<String, ParsedSql>(
			DEFAULT_CACHE_LIMIT, 0.75f, true) {
		@Override
		protected boolean removeEldestEntry(Map.Entry<String, ParsedSql> eldest) {
			return size() > getCacheLimit();
		}
	};

	/**
	 * Specify the maximum number of entries for this template's SQL cache.
	 * Default is 256.
	 */
	public void setCacheLimit(int cacheLimit) {
		this.cacheLimit = cacheLimit;
	}

	/**
	 * Return the maximum number of entries for this template's SQL cache.
	 */
	public int getCacheLimit() {
		return this.cacheLimit;
	}
	
	public SQLParser getSqlParser() {
		return sqlParser;
	}

	public void setSqlParser(SQLParser sqlParser) {
		this.sqlParser = sqlParser;
	}

	@Override
	public SqlRowSet queryForSqlRowSet(String sql, DataSource dataSource,
			Context context) throws SQLException {
		JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
		return jdbcTemplate.query(getPreparedStatementCreator(sql, dataSource,context),
				new SqlRowSetResultSetExtractor());
	}

	private PreparedStatementCreator getPreparedStatementCreator(String sql,DataSource dataSource,
			Context context) throws SQLException {
		if(sqlParser!=null){
			LOGGER.logMessage(LogLevel.DEBUG, "before SQLParser SQL：{0}",sql);
			DatabaseType databaseType = getDatabaseTypeWithDataSource(dataSource);
			sql=sqlParser.parse(databaseType, sql, context);
			LOGGER.logMessage(LogLevel.DEBUG, "after SQLParser SQL：{0}",sql);
		}
		SqlParameterSource paramSource = new MapSqlParameterSource(
				context.getTotalItemMap());
		ParsedSql parsedSql = getParsedSql(sql);
		String sqlToUse = NamedParameterUtils.substituteNamedParameters(
				parsedSql, paramSource);
		Object[] params = NamedParameterUtils.buildValueArray(parsedSql,
				paramSource, null);
		List<SqlParameter> declaredParameters = NamedParameterUtils
				.buildSqlParameterList(parsedSql, paramSource);
		PreparedStatementCreatorFactory pscf = new PreparedStatementCreatorFactory(
				sqlToUse, declaredParameters);
		return pscf.newPreparedStatementCreator(params);
	}

	private DatabaseType getDatabaseTypeWithDataSource(DataSource dataSource)
			throws SQLException {
		String databaseTypeStr;
		Connection connection = dataSource.getConnection();
		try {
			databaseTypeStr = connection.getMetaData()
					.getDatabaseProductName();
		} finally {
			if (connection != null) {
				connection.close();
			}
		}
		DatabaseType databaseType = DatabaseType.valueFrom(databaseTypeStr);
		return databaseType;
	}

	@Override
	public int execute(String sql, DataSource dataSource, Context context)throws SQLException  {
		JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
		return jdbcTemplate.update(getPreparedStatementCreator(sql,dataSource, context));
	}

	protected ParsedSql getParsedSql(String sql) {
		if (getCacheLimit() <= 0) {
			return NamedParameterUtils.parseSqlStatement(sql);
		}
		synchronized (this.parsedSqlCache) {
			ParsedSql parsedSql = this.parsedSqlCache.get(sql);
			if (parsedSql == null) {
				parsedSql = NamedParameterUtils.parseSqlStatement(sql);
				this.parsedSqlCache.put(sql, parsedSql);
			}
			return parsedSql;
		}
	}

	@Override
	public void extractResultSetCallback(String sql, DataSource dataSource,
			Context context, ResultSetCallback callback)throws SQLException {
		JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
		jdbcTemplate.query(getPreparedStatementCreator(sql, dataSource,context),
				new TinyResultSetCallback(callback));
	}

}
